package cn.yjh.support;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.yjh.annotation.RPCMapping;
import cn.yjh.annotation.RPCService;
import cn.yjh.exception.ServiceNotFoundException;
import cn.yjh.exception.ServicesException;
import cn.yjh.server.support.HttpStatus;
import cn.yjh.server.support.Request;
import cn.yjh.server.support.Response;
import cn.yjh.utils.AnnotationElementUtils;
import cn.yjh.utils.JavaSsistUtils;

/**
 * @DESC:
 * @AUTHOR: YouJinhua
 * @DATE: 2022-01-11 11:47:25
 * @VERSION: 1.0
 */
public class ServiceHandler {

    Logger logger = LoggerFactory.getLogger(ServiceHandler.class);

    private ServiceFactory serviceFactory = null;

    private Map<String,Invoker> mapping = new HashMap<>(256);

    public ServiceHandler(String scanPackage) {
        this.serviceFactory = new DefaultServiceFactory(scanPackage);
        initServiceHandler();
    }

    private void initServiceHandler() {
        ServiceRegister register = (ServiceRegister) serviceFactory;
        Set<ServiceDefinition> definitions = register.getDefinitions();
        Iterator<ServiceDefinition> iterator = definitions.iterator();
        try{
            while (iterator.hasNext()){
                ServiceDefinition next = iterator.next();
                Class clazz = next.serviceClazz;
                if(clazz.isInterface())
                    continue;
                RPCService rpcService = (RPCService) next.anno;;
                String baseURL = rpcService.path();
                Method[] methods = clazz.getDeclaredMethods();
                for(Method method : methods){
                    if(method.isAnnotationPresent(RPCMapping.class)){
                        RPCMapping rpcMapping = AnnotationElementUtils.getMergeAnnotation(method,RPCMapping.class);
                        String[] paths = rpcMapping.path();
                        List<String> paramsName = JavaSsistUtils.getParamsName(method.getName(), clazz);
                        List<Class> paramsType = JavaSsistUtils.getParamsType(method.getName(), clazz);
                        for(String path : paths){
                            String url = baseURL + path;
                            if(mapping.containsKey(url)){
                                throw new RuntimeException("");
                            }
                            mapping.put(url,new Invoker(url,next.defaultServiceName,method,paramsName,paramsType));
                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void process(Request request, Response response) throws ServiceNotFoundException {
        Object result = null;
        Invoker invoker = mapping.get(request.getUrl());
        if(invoker == null) {
            throw new ServiceNotFoundException("NOT FOUND RPC SERVICE MAPPING:"+request.getUrl());
        }else{
            logger.info("invoke {}.{}", invoker.serviceName, invoker.method.getName());
            invoker.method.setAccessible(true);
            Object service = null;
            try {
                service = serviceFactory.getService(invoker.serviceName);
            } catch (ServicesException e) {
                throw new ServiceNotFoundException("NOT FOUND RPC SERVICE MAPPING:"+request.getUrl());
            }
            Map<String, Object> params = request.getParams();
            List<Class> paramsType = invoker.paramsType;
            List<String> paramsName = invoker.paramsName;
            Object[] args = new Object[paramsType.size()];
            for(int i=0;i<paramsName.size();i++){
                String paramName = paramsName.get(i);
                Class paramType = paramsType.get(i);
                if(paramType == Request.class){
                    args[i] = request;
                    continue;
                }
                if(paramType == Response.class){
                    args[i] = response;
                    continue;
                }
                Object arg = params.get(paramName);
                if(arg == null){
                    logger.error("request param: {} not present", paramName);
                }
                args[i] = arg;
            }
            try {
                result = invoker.method.invoke(service,args);
                response.setStatus(HttpStatus.OK);
                response.setData(result);
                return;
            } catch (Exception e) {
                logger.error("invoke {}.{} throw exception: {}",invoker.serviceName, invoker.method.getName(), e);
                response.setStatus(HttpStatus.SERVER_ERROR);
                response.setMsg(new String("SERVER ERROR: INVOKE "+invoker.serviceName+"."+invoker.method+" FAILD"));
                e.printStackTrace();
            }
        }
    }
}
